package ru.amse.stroganova.graph;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import com.sun.corba.se.impl.orbutil.graph.Graph;

/**
 * This class provides a skeletal implementation of graph.
 * 
 * @author Dasha Stroganova
 * 
 */
public abstract class AbstractGraph {

	private final Set<Vertex> vertices;

	/**
	 * Creates an empty graph.
	 * 
	 */
	public AbstractGraph() {
		vertices = new HashSet<Vertex>();
	}

	/**
	 * This method returns a set vertices of this graph.
	 * 
	 * @return an unmodifiable set of vertices.
	 */
	public Set<Vertex> getVertices() {
		return Collections.unmodifiableSet(vertices);
	}

	/**
	 * Adds a new isolated vertex to this graph and returns it.
	 * 
	 * @return new vertex
	 */
	//TODO may be at some point this method should be eliminated
	public Vertex addVertex() {
		Vertex vertex = new Vertex();
		vertices.add(vertex);
		return vertex;
	}

	/**
	 * Adds given vertex to graph. Vertex shouldn't be in graph and shouldn't have any edges.
	 * 
	 * @param vertex vertex to be added
	 */
	public void addVertex(Vertex vertex) {
		if (vertices.contains(vertex)) {
			throw new IllegalArgumentException("Vertex already in graph");
		}
		if ((vertex.getOutgoingEdges().size() != 0) || (vertex.getIncomingEdges().size() != 0)) {
			throw new IllegalArgumentException("Vertex has adjusted edges");
		}
		vertices.add(vertex);
	}
	
	/**
	 * Removes given vertex from this graph.
	 * 
	 * @param vertex
	 *            vertex to remove
	 * @return true if given vertex was in graph; false otherwise
	 * 
	 */
	public abstract boolean removeVertex(Vertex vertex);

	/**
	 * Veryfies whether graph is directed or not.
	 * 
	 * @return true if it is; false otherwise
	 */
	public abstract boolean isDirected();
	
	/**
	 * Checkes whether two given vertices are connected with an edge.
	 * 
	 * @param source
	 *            source vertex
	 * @param destination
	 *            destination vertex
	 * @return true if an edge exists; false otherwise
	 */
	public boolean areConnected(Vertex source, Vertex destination) {
		return (getConnectingEdge(source, destination) != null);
	}

	/**
	 * Returns the edge connecting given vertices.
	 * 
	 * @param source
	 *            source vertex
	 * @param destination
	 *            destination vertex
	 * @return edge connecting vertices if thet are connected; null otherwise
	 */
	public abstract Edge getConnectingEdge(Vertex source, Vertex destination);

	/**
	 * Connects given vertices with an edge and returns it. Multiple edges are
	 * prohibited.
	 * 
	 * @param source
	 *            source vertex
	 * @param destination
	 *            destination vertex
	 * @return new edge
	 */
//	TODO may be at some point this method should be eliminated
	public abstract Edge connect(Vertex source, Vertex destination);

	/**
	 * Connects given vertices with an edge with the given weight and returns
	 * it. Multiple edges are prohibited.
	 * 
	 * @param source
	 *            source vertex
	 * @param destination
	 *            destination vertex
	 * @param weight
	 *            egde weight
	 * @return new weighted edge
	 */
	public abstract Edge connect(Vertex source, Vertex destination, int weight);

	/**
	 * Adds given edge to graph. Both edge ends should already be in graph and be not connected.
	 * 
	 * @param edge edge to be added
	 */
	public abstract void addEdge(Edge edge);
	
	/**
	 * 
	 * Disconnects given vertices.
	 * 
	 * @param source
	 *            source vertex
	 * @param destination
	 *            destination vertex
	 * @return true if given vertices were connected; false otherwise
	 */
	public abstract boolean disconnect(Vertex source, Vertex destination);

	/**
	 * Checkes all contracts of connect {@link Graph#connect()} method. Vertices
	 * must be in this graph and must be disconnected. Otherwise
	 * <tt>IllegalArgumentException</tt> is thrown.
	 * 
	 * @param source
	 *            source vertex
	 * @param destination
	 *            destination vertex
	 */
	protected void testVertexToConnect(Vertex source, Vertex destination) {
		testVertices(source, destination);
		if (areConnected(source, destination)) {
			throw new IllegalArgumentException("Vertices already connected");
		}
	}

	/**
	 * Enables subclasses to add vertices to graph.
	 * 
	 * @param vertex
	 *            vertex to add
	 */
	protected void addVertexToGraph(Vertex vertex) {
		vertices.add(vertex);
	}

	/**
	 * Enables subclasses to remove vertices from graph.
	 * 
	 * @param vertex
	 *            vertex to remove.
	 */
	protected void removeVertexFromGraph(Vertex vertex) {
		vertex.removeEdges();
		vertices.remove(vertex);
	}

	/**
	 * Checks whether the given vertex is in graph.
	 * 
	 * @param vertex
	 *            vertex
	 * @return true if vertex is in graph; false otherwise
	 */
	protected boolean isVertexInGraph(Vertex vertex) {
		return vertices.contains(vertex);
	}

	/**
	 * Checks whether the given vertices are in graph. If one is not
	 * <tt>IllegalArgumentException</tt> is thrown.
	 * 
	 * @param source
	 *            source vertex
	 * @param destination
	 *            destination vertex
	 */
	protected void testVertices(Vertex source, Vertex destination) {
		if (!isVertexInGraph(source)) {
			throw new IllegalArgumentException(
					"Source vertex not in this graph.");
		}
		if (!isVertexInGraph(destination)) {
			throw new IllegalArgumentException(
					"Destination vertex not in this graph.");
		}
	}
}
